#ifndef H_Profile
#define H_Profile

/**
 * @file Profile.hh
 * @author Michael Iatauro
 * @brief Defines a base class for computing Resource level profiles
 * @date March, 2006
 * @ingroup Resource
 */

#include "Domains.hh"
#include "PlanDatabaseDefs.hh"
#include "ConstraintEngineListener.hh"
#include "ConstraintEngineDefs.hh"
#include "Constraint.hh"
#include "CommonDefs.hh"
#include "ResourceDefs.hh"
#include "FVDetector.hh"
#include "Debug.hh"
#include "Engine.hh"
#include "Factory.hh"

#include <map>
#include <utility>

#include <boost/smart_ptr/shared_ptr.hpp>

namespace EUROPA {

    /**
     * @class Profile
     * @brief The base class for managing Resource level profiles
     *
     * The Profile class is, in general, responsible for managing the computation of bounds around the level of some Resource.
     * It does this by performing some computation on a set of Transactions, which are <timepoint, quantity> pairs, with possible
     * flexibility in either the timepoint or the quantity.  The computed profile is made available to other classes (an envelope
     * calculator, for instance) via a ProfileIterator.
     * The base Profile class is specifically responsible for maintaining the structures representing the profile (Instants) and
     * for causing subclasses to recalculate the profile.
     * When should we recalculate?
     */
class Profile : public FactoryObj {
 public:

  /**
   * @brief Constructor.
   * @param flawDetector The object responsible for detecting flaws and violations during level calculation.
   */
  Profile(const PlanDatabaseId ce, const FVDetectorId flawDetector);
  virtual ~Profile();
  ProfileId getId() {return m_id;}

  /**
   * @brief Adds a Transaction to the profile.  Creates Instants for the lower and upper bounds of the timepoint if necessary,
   * adds the Transaction to every Instant in the interval [timepoint.start timepoint.end], and marks the need for recalculation.
   * This method also adds listeners to the quantity and timepoint in order to track changes in their values.
   * @param e The Transaction.
   */
  virtual void addTransaction(const TransactionId e);

  /**
   * @brief Removes a Transaction from the profile.  Removes the Transaction from all instants in the interval [timepoint.start timepoint.end],
   * marks the need for recalculation, and removes the listeners on the quantity and timepoint.
   * @param e The Transaction.
   */
  virtual void removeTransaction(const TransactionId e);

  /**
   * @brief Get all the transactions in the profile.
   */
  const std::set<TransactionId>& getAllTransactions() const {return m_transactions;}

  /**
   * @brief Gets the bounds of the profile at a given point in time. Calling this method may cause recalculation.
   * @param time The time at which to get the level.
   * @param dest The interval into which the computed levels are stored.
   */
  virtual void getLevel(const eint time, IntervalDomain& dest);
      
  /**
   * @brief Validates the data structures.  Maybe this should be private.
   */
  virtual bool isValid();

  /**
   * @brief Forces a recomputation of the profile.
   */
  void recompute();

  const PlanDatabaseId getPlanDatabase() const {return m_planDatabase;}


  virtual void getTransactionsToOrder(const InstantId inst, std::vector<TransactionId>& results);

  const ResourceId getResource() const {return m_detector->getResource();}

  /**
   * @brief Gets the Instant with the greatest time that is not greater than the given time.
   * @return An iterator pointing to the instant.
   */
  std::map<eint, InstantId>::iterator getGreatestInstant(const eint time);

  /**
   * @brief Gets the Instant with the least time not less than the given time.
   * @return An iterator pointing to the instant.
   */
  std::map<eint, InstantId>::iterator getLeastInstant(const eint time);

  const std::map<eint, InstantId>& getInstants() {return m_instants;}
      
  InstantId getInstant(const eint time) const;

 private:
  friend class ProfilePropagator;
  friend class ProfileIterator;
  friend class Instant;

  /**
   * @class VariableListener
   * @brief Informs the profile of a change in either a quantity or time variable.
   */
  class VariableListener : public ConstraintEngineListener {
   public:
    VariableListener(const ConstraintEngineId constraintEngine,
                     const ProfileId profile,
                     const TransactionId trans,
                     const std::vector<ConstrainedVariableId>& scope,
		     const bool isQuantity = false);
    
    ProfileId getProfile() const {return m_profile;}
   private:
    void notifyChanged(const ConstrainedVariableId variable,
                   const DomainListener::ChangeType& changeType);
    ProfileId m_profile;
    TransactionId m_trans;
    std::vector<ConstrainedVariableId> m_scope;
    bool m_isQuantity;
  };

  class ConstraintAdditionListener : public ConstrainedVariableListener {
   public:
    ConstraintAdditionListener(const ConstrainedVariableId var, TransactionId tid, ProfileId profile);
    ~ConstraintAdditionListener();
    void notifyDiscard();
    void notifyConstraintAdded(const ConstraintId constr, unsigned int argIndex);
    void notifyConstraintRemoved(const ConstraintId constr, unsigned int argIndex);
   private:
    ProfileId m_profile;
    TransactionId m_tid;
  };

  struct ConstraintMessage {
    ConstrainedVariableId var;
    int index;
    bool addition;
    ConstraintMessage(ConstrainedVariableId _var, int _index, bool _addition)
        : var(_var), index(_index), addition(_addition) {}
  };

  /**
   * @class ConstraintRemovalListener
   * @brief Listens for removal messages on constraints.
   *
   * Because it's possible for the ConstraintAdditionListener to get removed before a constraint it's listening for,
   * this class has been added to catch any final messages about removal.
   */

  class ConstraintRemovalListener : public ConstraintEngineListener {
   public:
    ConstraintRemovalListener(const ConstraintEngineId ce, ProfileId profile);
    void notifyRemoved(const ConstraintId constr);
    void notifyRemoved(const ConstrainedVariableId v) {ConstraintEngineListener::notifyRemoved(v);}
   private:
    ProfileId m_profile;
  };

  bool hasConstraint(const ConstraintId constr) const;

 protected:
  ProfileId m_id;
  unsigned int m_changeCount; /**< The number of times that the profile has changed.  Used to detect stale iterators.*/
  bool m_needsRecompute; /**< A flag indicating the necessity of profile recomputation*/
  unsigned int m_constraintKeyLb; /**< The lower bound on the constraint key when searching for new constraints. */
  PlanDatabaseId m_planDatabase; /**< The plan database.  Used for creating the variable listeners. */
  FVDetectorId m_detector; /**< The flaw and violation detector. */
  std::set<TransactionId> m_transactions; /**< The set of Transactions that impact this profile. */
  std::multimap<TransactionId, boost::shared_ptr<VariableListener> > m_variableListeners; /**< The listeners on the Transactions. */
  std::map<TransactionId, boost::shared_ptr<ConstraintAdditionListener> > m_otherListeners;
  std::map<ConstrainedVariableId, TransactionId> m_transactionsByTime;
  ConstraintSet m_temporalConstraints;
  ConstraintEngineListenerId m_removalListener;
  std::map<eint, InstantId> m_instants; /**< A map from times to Instants. */
  ProfileIteratorId m_recomputeInterval; /**< The stored interval of recomputation.*/

  bool hasTransactions() {return !m_transactions.empty();}

  void transactionTimeChanged(const TransactionId e, const DomainListener::ChangeType& change);

  void transactionQuantityChanged(const TransactionId e, const DomainListener::ChangeType& change);

  //these methods need to marshal the data between two calls
  void temporalConstraintAdded(const ConstraintId c, const ConstrainedVariableId var, unsigned int argIndex);

  void temporalConstraintRemoved(const ConstraintId c, const ConstrainedVariableId var, unsigned int argIndex);

  /**
   * @brief Recompute the profile.  Iterates over a stored interval of time.
   * It is expected that the first Instant in the interval actually precede the first change
   * so that the flaw and violation detector can be initialized.
   */
  void handleRecompute();
  /**
   * @brief Hanlde invoked at the end of handleRecompute
   */
  virtual void postHandleRecompute(const eint& endTime, const std::pair<edouble,edouble>& endDiff);
  /**
   * @brief Initialize a recomputation with level data from the given Instant.
   * This function is expected to re-compute the levels for the given instant!
   */
  virtual void initRecompute(InstantId inst) = 0;

  /**
   * @brief Initialize a recomputation with no level data.  Used when there is no Instant preceeding the recomputation interval.
   */
  virtual void initRecompute() = 0;

  /**
   * @brief Recompute the levels for a particular Instant.
   * @param inst The Instant to be recomputed.
   */
  virtual void recomputeLevels( InstantId prev, InstantId inst ) = 0;

  /**
   * @brief Helper method for subclasses to respond to a transaction being added.
   * @param e The Transaction that was added
   */
  virtual void handleTransactionAdded(const TransactionId e);

  /**
   * @brief Helper method for subclasses to respond to a transaction being removed.
   * @param e The Transaction that was removed
   */
  virtual void handleTransactionRemoved(const TransactionId e);

  /**
   * @brief Helper method for subclasses to respond to a transaction's timepoint changing.
   * The base method handles the addition/removal of the transaction to/from instants.
   * @param e The Transaction whose time variable changed.
   * @param change The type of the change
   */
  virtual void handleTransactionTimeChanged(const TransactionId e, const DomainListener::ChangeType& change);

  /**
   * @brief Helper method for subclasses to respond to a transaction's quantity changing.
   * @param e The Transaction whose quantity variable changed.
   * @param change The type of the change
   */
  virtual void handleTransactionQuantityChanged(const TransactionId e, const DomainListener::ChangeType& change);

  //NOTE: DON'T FORGET TO IMPLEMENT THE DEFAULT  BEHAVIOR!!!
  /**
   * @brief Helper method for subclasses to respond to a temporal constraint being added between two transactions.
   */
  virtual void handleTemporalConstraintAdded(const TransactionId predecessor,
                                             const unsigned int preArgIndex,
                                             const TransactionId successor,
                                             const unsigned int sucArgIndex);

  /**
   * @brief Helper method for subclasses to respond to a temporal constraint being removed between two transactions.
   */
  virtual void handleTemporalConstraintRemoved(const TransactionId predecessor,
                                               const unsigned int preArgIndex,
                                               const TransactionId successor,
                                               const unsigned int sucArgIndex);


  /**
   * @brief Helper method for subclasses to respond to a variable deletion (the listener on that variable may need destruction).
   * @param t The transaction that has been removed.
   */
  void handleTransactionVariableDeletion(const TransactionId t);

  bool needsRecompute() const {return m_needsRecompute;}

  std::string toString() const;

  // PHM Some refactoring needed so that customized subclass can
  // support reftimes.  Does not change this class functionality.
  /**
   * @brief Determines whether an instant contains a "significant"
   * transaction (i.e., at its upper/lower bound) that changes the
   * profile at that instant.
   *
   * @param instant The instant
   */
  virtual bool containsChange(const InstantId instant);
  /**
   * @brief Conditionally adds Instants for the upper and lower
   * bounds of the Transaction's time.
   *
   * @param t The transaction
   */
  virtual void addInstantsForBounds(const TransactionId t);

  /**
   * @brief Create an instant for a time.
   * @param time The time
   */
  void addInstant(const eint time);
  /**
   * @brief Eliminate an instant for a time.
   * @param time The time
   */
  void removeInstant(const eint time);

  edouble getInitCapacityLb() const;

  edouble getInitCapacityUb() const;

 private:
  /** 
   * @brief Handle an addition or removal message on a temporal constraint.
   * Waits for two consecutive addition or removal messages in order to ensure that both variables in the scope of the 
   * constraint are resource-temporal variables.  
   * 
   * @param c The constraint.
   * @param var The resource-related temporal variable the constraint is on.
   * @param argIndex The index of the resource-related temporal variable in the scope of the constraint.
   * @param addition True if the message is an addition, false otherwise.
   */      
  void handleConstraintMessage(const ConstraintId c, const ConstrainedVariableId var,
                               unsigned int argIndex, bool addition);

  /** 
   * @brief Checks the m_constraintsForNotification map for internal consistency.
   * @return true if the map is consistent.  False otherwise.
   */
  bool checkMessageConsistency();
};

/**
 * @class ProfileIterator
 * @brief Used to iterate over a calculated profile.  Each call to next() advances the iterator to the next point in time at which a
 * level changes.  Directly constructible only inside the Profile class. (Is this a good idea?)
 */
class ProfileIterator {
 public:

  /**
   * @brief Constructor for an iterator over a profile.
   * @param prof The profile to be iterated.
   * @param startTime The time to begin iteration.  The time of the first Instant is guaranteed to be the greatest time not greater than this value.
   * @param endTime The time to end iteration.  The time of the last Instant is guaranteed to be the greatest time not greater than this value.
   */
  ProfileIterator(const ProfileId prof, const eint startTime = MINUS_INFINITY, const eint endTime = PLUS_INFINITY);
      
  ~ProfileIterator() {m_id.remove();}

  /**
   * @brief Determine whether or not this ProfileIterator has iterated past its end time.
   */
  bool done() const;

  /**
   * @brief Gets the time of the current position of this ProfileIterator
   * @return The time
   */
  eint getTime() const;
      
  /**
   * @brief Gets the value of the lower bound of the profile at the current instant in time.
   * @return The value of the profile.
   */
  edouble getLowerBound() const;

  /**
   * @brief Gets the value of the upper bound of the profile at the current instant in time.
   * @return The value of the profile.
   */
  edouble getUpperBound() const;

  /**
   * @brief Gets the current instant.
   * @return The instant.
   */
  InstantId getInstant() const;
  /**
   * @brief Steps the ProfileIterator to the next instant in time.
   * @return true if the ProfileIterator ends on a valid Instant, false otherwise.
   */
  bool next();

  ProfileIteratorId getId(){return m_id;}

  /**
   * @brief Determines if the interval of the iterator is still in synch with the actual profile.
   */
  bool isStale() const;

  eint getStartTime() const;
  eint getEndTime() const;

 protected:
  ProfileIteratorId m_id;
  ProfileId m_profile;
  unsigned int m_changeCount; /**< A copy of the similar variable in Profile when this iterator was instantiated.  Used to detect staleness. */
  eint m_startTime, m_endTime;
  std::map<eint, InstantId>::const_iterator m_start, m_end, m_realEnd; /**< The start and end times over which this iterator goes*/
};

    class ProfileArgs : public FactoryArgs
    {
    public:
        ProfileArgs(const PlanDatabaseId db, const FVDetectorId detector)
            : m_db(db)
        	, m_detector(detector)
        {
        }

        const PlanDatabaseId m_db;
        const FVDetectorId m_detector;
    };

    template<class ProfileType>
    class ProfileFactory : public Factory {
    public:
      ProfileFactory(const std::string& name) : Factory(name) {}

      virtual EUROPA::FactoryObjId createInstance(const EUROPA::FactoryArgs& fa)
      {
        const ProfileArgs& args = dynamic_cast<const ProfileArgs&>(fa);
          return (new ProfileType(args.m_db, args.m_detector))->getId();
      }
    };

    #define REGISTER_PROFILE(MGR, CLASS, NAME) (MGR->registerFactory((new EUROPA::ProfileFactory<CLASS>(#NAME))->getId()));

/*
 * A Profile keeps track of upper&lower bounds of a resource's attribute over time.
 * An ExplicitProfile is a profile whose values are explicitly specified (instead of being computed from usage or other information)
 * An ExplicitProfile is a piecewise constant function, we may consider supporting other types in the future.
 * For the time being a resource's capacity will be represented using an ExplicitProfile.
 * Limits could also be represented by an ExplicitProfile if we see the need to move beyond a single {upperBound,lowerBound} pair.
 *
 * TODO: Profile needs to be refactored into an interface that both the ExplicitProfile and (a new) ComputedProfile class can implement.
 * The current Profile class represents the Level (or availability) profile for a resource and would be better characterized
 * as a ComputedProfile
 */
class ExplicitProfile {
 public:
ExplicitProfile(edouble lb, edouble ub);
virtual ~ExplicitProfile();

ExplicitProfileId getId();

void setValue(eint time, edouble lb, edouble ub);
void removeValue(eint time);
// TODO: provide ExplicitProfileIterator instead?
const std::map< eint,std::pair<edouble,edouble> >& getValues() const;

const std::pair<edouble,edouble>& getValue(eint time) const;
const std::pair<edouble,edouble>& getEarliestValue() const;

 protected:
ExplicitProfileId m_id;
std::map< eint,std::pair<edouble,edouble> > m_values;
};
}

#endif
